﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

using System;
using Tesla.Content;
using Tesla.Core;
using Tesla.Graphics.Implementation;

namespace Tesla.Graphics {
    /// <summary>
    /// RasterizerState object that controls how 3D geometry is rasterized.
    /// </summary>
    public class RasterizerState : RenderState {
        private RasterizerStateImplementation _impl;
        private RenderStateKey _key;

        /// <summary>
        /// Gets a built-in state object where culling is disabled.
        /// </summary>
        public static RasterizerState CullNone {
            get;
            internal set;
        }

        /// <summary>
        /// Gets a built-in state object where back faces are culled and front faces have
        /// a clockwise vertex winding. This is the default state.
        /// </summary>
        public static RasterizerState CullBackClockwiseFront {
            get;
            internal set;
        }

        /// <summary>
        /// Gets a built-in state object where back faces are culled and front faces have a counterclockwise
        /// vertex winding.
        /// </summary>
        public static RasterizerState CullBackCounterClockwiseFront {
            get;
            internal set;
        }

        /// <summary>
        /// Gets a built-in state object where culling is disabled and fillmode is wireframe.
        /// </summary>
        public static RasterizerState CullNoneWireframe {
            get;
            internal set;
        }

        /// <summary>
        /// Get if this render state has been bound to the pipeline.
        /// </summary>
        public override bool IsBound {
            get {
                return _impl.IsBound;
            }
        }

        /// <summary>
        /// Gets the <see cref="RenderStateKey"/> that is used to compare this state
        /// to another state.
        /// </summary>
        public override RenderStateKey RenderStateKey {
            get {
                if(!_impl.IsBound) {
                    return new RenderStateKey(GetHashCode(), StateType);
                }
                return _key;
            }
        }

        /// <summary>
        /// Get the underlying state object that is bound to the graphics pipeline.
        /// </summary>
        public override GraphicsResourceImplementation Implementation {
            get {
                return _impl;
            }
        }

        /// <summary>
        /// Get or set how primitives are to be culled. Default
        /// is <see cref="CullMode.Back"/>.
        /// </summary>
        public CullMode Cull {
            get {
                return _impl.Cull;
            }
            set {
                _impl.Cull = value;
            }
        }

        /// <summary>
        /// Get or set the vertex winding of a primitive (front face). Default
        /// is <see cref="Tesla.Graphics.VertexWinding.CounterClockwise"/>.
        /// </summary>
        public VertexWinding VertexWinding {
            get {
                return _impl.VertexWinding;
            }
            set {
                _impl.VertexWinding = value;
            }
        }

        /// <summary>
        /// Get or set the fill mode of a primitive. Default is
        /// <see cref="FillMode.Solid"/>.
        /// </summary>
        public FillMode Fill {
            get {
                return _impl.Fill;
            }
            set {
                _impl.Fill = value;
            }
        }

        /// <summary>
        /// Get or set the depth bias. Default is zero.
        /// </summary>
        public int DepthBias {
            get {
                return _impl.DepthBias;
            }
            set {
                _impl.DepthBias = value;
            }
        }

        /// <summary>
        /// Get or set the slope scaled depth bias. Default is zero.
        /// </summary>
        public float SlopeScaledDepthBias {
            get {
                return _impl.SlopeScaledDepthBias;
            }
            set {
                _impl.SlopeScaledDepthBias = value;
            }
        }

        /// <summary>
        /// Get or set if multi sampling anti aliasing should be enabled. 
        /// Default is true.
        /// </summary>
        public bool EnableMultiSampleAntiAlias {
            get {
                return _impl.EnableMultiSampleAntiAlias;
            }
            set {
                _impl.EnableMultiSampleAntiAlias = value;
            }
        }

        /// <summary>
        /// Get or set if scissor testing should be enabled. Default is false.
        /// </summary>
        public bool EnableScissorTest {
            get {
                return _impl.EnableScissorTest;
            }
            set {
                _impl.EnableScissorTest = value;
            }
        }

        /// <summary>
        /// Get the enumeration describing this render state.
        /// </summary>
        public override RenderStateType StateType {
            get {
                return RenderStateType.RasterizerState;
            }
        }

        /// <summary>
        /// Creates a new instance of <see cref="RasterizerState"/> with default values.
        /// </summary>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if creating the underlying state implementation fails.</exception>
        public RasterizerState() {
            IRenderSystemProvider renderSystem = Engine.Services.GetService<IRenderSystemProvider>();
            if(renderSystem == null) {
                Dispose();
                throw new TeslaException("Render system provider not set, cannot create graphics resource implementation.");
            }

            base.RenderSystem = renderSystem;

            try {
                _impl = renderSystem.CreateRasterizerStateImplementation();
            } catch(Exception e) {
                Dispose();
                throw new TeslaException("Error creating underlying implementation, see inner exception for details.", e);
            }

            SetDefaults();
        }

        /// <summary>
        /// Creates a new instance of <see cref="RasterizerState"/> with default values.
        /// </summary>
        /// <param name="renderSystem">Render system used to create the underlying implementation.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if the render system is null.</exception>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if creating the underlying state implementation fails.</exception>
        public RasterizerState(IRenderSystemProvider renderSystem) {
            if(renderSystem == null) {
                Dispose();
                throw new ArgumentNullException("renderSystem", "Render system cannot be null.");
            }

            base.RenderSystem = renderSystem;

            try {
                _impl = renderSystem.CreateRasterizerStateImplementation();
            } catch(Exception e) {
                Dispose();
                throw new TeslaException("Error creating underlying implementation, see inner exception for details.", e);
            }

            SetDefaults();
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="RasterizerState"/> is reclaimed by garbage collection.
        /// </summary>
        ~RasterizerState() {
            Dispose(false);
        }

        private void SetDefaults() {
            //Set defaults
            _impl.Cull = CullMode.Back;
            _impl.VertexWinding = VertexWinding.Clockwise;
            _impl.DepthBias = 0;
            _impl.Fill = FillMode.Solid;
            _impl.EnableMultiSampleAntiAlias = true;
            _impl.EnableScissorTest = false;
            _impl.SlopeScaledDepthBias = 0;
        }

        /// <summary>
        /// Binds the rasterizer state to the graphics pipeline.
        /// </summary>
        public override void BindRenderState() {
            if(!IsBound) {
                _impl.BindRasterizerState();
                _key = new RenderStateKey(GetHashCode(), StateType);
            }
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>A hash code for this instance.</returns>
        public override int GetHashCode() {
            int hash = StateType.GetHashCode();
            hash = ((hash << 2) - hash) + ((_impl.EnableMultiSampleAntiAlias) ? 1 : 0);
            hash = ((hash << 2) - hash) + ((_impl.EnableScissorTest) ? 1 : 0);
            hash = ((hash << 2) - hash) + _impl.Cull.GetHashCode();
            hash = ((hash << 2) - hash) + _impl.VertexWinding.GetHashCode();
            hash = ((hash << 2) - hash) + _impl.Fill.GetHashCode();
            hash = ((hash << 2) - hash) + _impl.DepthBias;
            hash = ((hash << 2) - hash) + _impl.SlopeScaledDepthBias.GetHashCode();
            return hash;
        }

        /// <summary>
        /// Serializes the object and writes it to the output.
        /// </summary>
        /// <param name="output">Savable Output</param>
        public override void Write(ISavableWriter output) {
            output.WriteEnum<CullMode>("Cull", _impl.Cull);
            output.WriteEnum<VertexWinding>("VertexWinding", _impl.VertexWinding);
            output.WriteEnum<FillMode>("Fill", _impl.Fill);
            output.Write("DepthBias", _impl.DepthBias);
            output.Write("SlopeScaledDepthBias", _impl.SlopeScaledDepthBias);
            output.Write("EnableMultiSampleAntiAlias", _impl.EnableMultiSampleAntiAlias);
            output.Write("EnableScissorTest", _impl.EnableScissorTest);
        }

        /// <summary>
        /// Deserializes the object and populates it from the input.
        /// </summary>
        /// <param name="input">Savable input</param>
        /// <exception cref="Tesla.Core.TeslaException">Thrown if creating the underlying state implementation fails or the render
        /// system is not set.</exception>
        public override void Read(ISavableReader input) {
            IRenderSystemProvider renderSystem = input.RenderSystem;
            if(renderSystem == null) {
                Dispose();
                throw new TeslaException("Render system provider not set, cannot create graphics resource implementation.");
            }

            base.RenderSystem = renderSystem;
            try {
                _impl = renderSystem.CreateRasterizerStateImplementation();
            } catch(Exception e) {
                Dispose();
                throw new TeslaException("Error creating underlying implementation, see inner exception for details.", e);
            }

            _impl.Cull = input.ReadEnum<CullMode>();
            _impl.VertexWinding = input.ReadEnum<VertexWinding>();
            _impl.Fill = input.ReadEnum<FillMode>();
            _impl.DepthBias = input.ReadInt();
            _impl.SlopeScaledDepthBias = input.ReadSingle();
            _impl.EnableMultiSampleAntiAlias = input.ReadBoolean();
            _impl.EnableScissorTest = input.ReadBoolean();
        }
    }
}
